// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: donggu, jhkim

#pragma once

#include <algorithm>
#include <deque>
#include <string>
#include <utility>
#include <memory>
#include "coin/proto/coin_order_enums.pb.h"
#include "presto/quant/math/moving_average.h"
#include "presto/quant/math/ewma.h"
#include "presto/quant/math/moving_window.h"


namespace coin2::strategy::linear_model {

using coin::proto::OrderEvent;
using coin2::exchange::base::symbology::IProduct;

inline std::string GetProductKey(const IProduct* product) {
  return fmt::format("{}.{}", product->absolute_norm(), product->exchange_name());
}

inline std::string GetProductKey(const OrderEvent* oe) {
  return fmt::format("{}.{}", oe->symbol(), ExchangeTypeToString(oe->exchange_type()));
}

class ClearEdgeManager {
 public:
  ClearEdgeManager(
      int64_t add_clear_lean_per_window_sec,
      double add_clear_lean_bps)
      : add_clear_lean_per_window_sec_(add_clear_lean_per_window_sec),
        add_clear_lean_bps_(add_clear_lean_bps),
        prev_pos_stack_(0),
        prev_sample_time_(0L),
        clear_lean_bps(0) {}

  void UpdatePositionInStack(int64_t timestamp, double pos_stack) {
    if (prev_sample_time_ == 0) {
      prev_sample_time_ = timestamp;
    }
    if (add_clear_lean_per_window_sec_ == 0) {
      return;
    }
    if (clear_lean_bps < 0) {
      // leaning to sell
      if (prev_pos_stack_ > pos_stack) {
        // sold? then clear lean status
        clear_lean_bps = 0;
        prev_pos_stack_ = pos_stack;
        prev_sample_time_ = timestamp;
      }
    } else if (clear_lean_bps > 0) {
      // leaning to buy
      if (prev_pos_stack_ < pos_stack) {
        // bought? then clear lean status
        clear_lean_bps = 0;
        prev_pos_stack_ = pos_stack;
        prev_sample_time_ = timestamp;
      }
    }

    if (prev_sample_time_
        + add_clear_lean_per_window_sec_ * Time::kNanosecondsPerSecond < timestamp) {
      prev_sample_time_ = timestamp;
      if (pos_stack > 0) {
        clear_lean_bps -= add_clear_lean_bps_;
      } else if (pos_stack < 0) {
        clear_lean_bps += add_clear_lean_bps_;
      }
    }
  }

 private:
  const int64_t add_clear_lean_per_window_sec_;
  const double add_clear_lean_bps_;
  double prev_pos_stack_;
  int64_t prev_sample_time_;

 public:
  double clear_lean_bps;
};

class PredictionInterval {
 public:
  explicit PredictionInterval(int64_t window_ns)
      : signal_window_(window_ns) {}

  virtual void UpdateSignal(int64_t timestamp, double signal) {
    signal_window_.Update(timestamp, signal);
  }

  std::pair<double, double> GetEdge(
        double signal,
        double nsigma,
        double min_edge,
        double max_edge) const {
    double sigstd = 0.0;
    if (signal_window_.num_popped() != 0) sigstd = signal_window_.simplestd();
    double varedge = std::clamp(sigstd * nsigma, min_edge, max_edge);
    LOG_EVERY_N(INFO, 100) << "SIGSTD " << sigstd << " VE " << varedge;
    return {signal - varedge, signal + varedge};
  }

  bool GetMeanStd(double* ret_mean, double* ret_std, bool use_warmup) {
    *ret_std = signal_window_.simplestd();
    *ret_mean = signal_window_.average();
    bool ready;
    if (signal_window_.num_popped() != 0) {
      ready = true;
    } else {
      ready = !use_warmup;
    }
    return ready;
  }

  bool GetSum(double* ret_sum, bool use_warmup) {
    *ret_sum = signal_window_.sum();
    bool ready;
    if (signal_window_.num_popped() != 0) {
      ready = true;
    } else {
      ready = !use_warmup;
    }
    return ready;
  }

  bool GetNSigmaBound(
      double signal,
      double nsigma,
      double* lb,
      double* ub,
      double edgecap) const {
    double sigstd = 0.0;
    if (signal_window_.num_popped() != 0) {
      sigstd = signal_window_.simplestd();
    }
    double varedge = nsigma * sigstd;
    varedge = std::min(varedge, edgecap);
    *lb = signal - varedge;
    *ub = signal + varedge;
    return true;
  }

  bool GetNSigmaBoundMinusMean(
      double signal,
      double nsigma,
      double* lb,
      double* ub,
      double edgecap,
      bool nrz_signal) const {
    double sigstd = 0.0;
    if (signal_window_.num_popped() != 0) {
      sigstd = signal_window_.simplestd();
      if (nrz_signal)
        signal -= signal_window_.average();
    }
    double varedge = nsigma * sigstd;
    varedge = std::min(varedge, edgecap);
    *lb = signal - varedge;
    *ub = signal + varedge;
    return true;
  }

 protected:
  presto::math::MovingAverageWindow<double> signal_window_;
};

class PredictionInterval2 : public PredictionInterval {
 public:
  explicit PredictionInterval2(int64_t window_ns)
      : PredictionInterval(window_ns) {}
  void UpdateSignal(int64_t timestamp, double signal) final {
    if (prev_update_ + 100e6 < timestamp) {
      prev_update_ = timestamp;
      signal_window_.Update(timestamp, signal);
    }
  }

 private:
  int64_t prev_update_ = 0L;
};

class PositionInterval {
 public:
  explicit PositionInterval(int64_t window_ns)
      : position_window_(window_ns) {}

  virtual void UpdatePosition(int64_t timestamp, double curr_pos) {
    position_window_.Update(timestamp, curr_pos);
  }

  bool GetSum(double* ret_sum, bool use_warmup) {
    *ret_sum = position_window_.sum();
    bool ready;
    if (position_window_.num_popped() != 0) {
      ready = true;
    } else {
      ready = !use_warmup;
    }
    return ready;
  }

 protected:
  presto::math::MovingAverageWindow<double> position_window_;
};

class PositionDoubleInterval {
 public:
  explicit PositionDoubleInterval(int64_t window1_ns, int64_t window2_ns)
      : position_window_(window1_ns, window2_ns) {}

  virtual void UpdatePosition(int64_t time1, int64_t time2, double curr_pos) {
    position_window_.Update(time1, time2, curr_pos);
  }

  bool GetSum(double* ret_sum, bool use_warmup) {
    *ret_sum = position_window_.sum();
    bool ready;
    if (position_window_.num_popped() != 0) {
      ready = true;
    } else {
      ready = !use_warmup;
    }
    return ready;
  }

 protected:
  presto::math::DoubleMovingAverageWindow<double> position_window_;
};

struct PreRebootStat {
  void Serialize(int64_t timestamp) {
    if (ma_ptr->num_elements() > 10) {
      ma_ready = true;
    } else {
      ma_ready = false;
    }

    if (disable_logging) return;

    if (last_logged_ts_ + log_interval_ns_ >= timestamp) return;

    coin::proto::StrategyStoredValue stored_value;
    stored_value.set_product_name(product_name);
    stored_value.set_value_type(ma_type);

    if (!ma_ready && !loaded) {
      return;
    } else if (!ma_ready && loaded) {
      stored_value.set_timestamp_begin(pre_reboot_ts_begin);
      stored_value.set_timestamp_end(pre_reboot_ts_end);
      stored_value.set_num_elements(pre_reboot_num_elements);
      stored_value.set_average(pre_reboot_avg);
      stored_value.set_simplestd(pre_reboot_std);
    } else if (loaded && ma_ptr->num_popped() == 0) {
      int64_t window_size = ma_ptr->window_size();
      int64_t time_passed = ma_ptr->back().first - ma_ptr->begin()->first;
      double weight = static_cast<double>(window_size - time_passed) /
          static_cast<double>(pre_reboot_window_len);
      int64_t pre_num_el = static_cast<int64_t>(pre_reboot_num_elements * weight);

      int64_t cur_num_el = ma_ptr->num_elements();
      weight = static_cast<double>(cur_num_el) /
          static_cast<double>(pre_num_el + cur_num_el);

      stored_value.set_timestamp_begin(ma_ptr->back().first - ma_ptr->window_size());
      stored_value.set_timestamp_end(ma_ptr->back().first);
      stored_value.set_num_elements(pre_num_el + ma_ptr->num_elements());
      stored_value.set_average(ma_ptr->average() * weight + pre_reboot_avg * (1 - weight));
      stored_value.set_simplestd(std::sqrt(
          std::pow(ma_ptr->simplestd(), 2) * weight +
          std::pow(pre_reboot_std, 2) * (1 - weight)));
    } else {
      stored_value.set_timestamp_begin(ma_ptr->begin()->first);
      stored_value.set_timestamp_end(ma_ptr->back().first);
      stored_value.set_num_elements(ma_ptr->num_elements());
      stored_value.set_average(ma_ptr->average());
      stored_value.set_simplestd(ma_ptr->simplestd());
    }

    strat_logger->WriteStratStoredValue(stored_value, true, timestamp);
    last_logged_ts_ = timestamp;

    LOG(INFO) << timestamp << " WriteStratStoredValue done\n" << stored_value.DebugString().c_str();
  }

  void Deserialize(coin::proto::StrategyStoredValue ssv) {
    if (ssv.timestamp_begin() == 0L) return;

    pre_reboot_ts_begin = ssv.timestamp_begin();
    pre_reboot_ts_end = ssv.timestamp_end();
    pre_reboot_window_len = pre_reboot_ts_end - pre_reboot_ts_begin;
    pre_reboot_num_elements = ssv.num_elements();
    pre_reboot_avg = ssv.average();
    pre_reboot_std = ssv.simplestd();
    loaded = true;
  }

  bool GetMeanStd(double* ret_mean, double* ret_std, bool use_pre) {
    bool use_loaded = use_pre && loaded && !disable_logging;

    if (!ma_ready && !use_loaded) {
      return false;
    } else if (!ma_ready && use_loaded) {
      *ret_mean = pre_reboot_avg;
      *ret_std = pre_reboot_std;
      return true;
    } else if (ma_ptr->num_popped() == 0 && !use_loaded) {
      *ret_mean = ma_ptr->average();
      *ret_std = ma_ptr->simplestd();
      return true;
    } else if (ma_ptr->num_popped() == 0 && use_loaded) {
      int64_t window_size = ma_ptr->window_size();
      int64_t time_passed = ma_ptr->back().first - ma_ptr->begin()->first;
      double weight = static_cast<double>(window_size - time_passed) /
          static_cast<double>(pre_reboot_window_len);
      int64_t pre_num_el = static_cast<int64_t>(pre_reboot_num_elements * weight);

      int64_t cur_num_el = ma_ptr->num_elements();
      weight = static_cast<double>(cur_num_el) /
          static_cast<double>(pre_num_el + cur_num_el);

      *ret_mean = (ma_ptr->average() * weight + pre_reboot_avg * (1 - weight));
      *ret_std = std::sqrt(
          std::pow(ma_ptr->simplestd(), 2) * weight +
          std::pow(pre_reboot_std, 2) * (1 - weight));
      return true;
    } else {
      *ret_mean = ma_ptr->average();
      *ret_std = ma_ptr->simplestd();
      return true;
    }
  }

  exchange::base::strategy_util::StrategyLogger* strat_logger;
  std::string product_name;
  int ma_type;
  presto::math::MovingAverageWindow<double>* ma_ptr;
  bool ma_ready = false;
  bool loaded = false;
  int64_t pre_reboot_ts_begin = 0L;
  int64_t pre_reboot_ts_end = 0L;
  int64_t pre_reboot_window_len = 0L;
  int64_t pre_reboot_num_elements;
  double pre_reboot_avg;
  double pre_reboot_std;
  int64_t last_logged_ts_ = 0L;
  int64_t log_interval_ns_ = 0L;
  bool disable_logging = false;
};

class SignalRMSEManager {
 public:
  SignalRMSEManager(
      int64_t y_horiz_ns,
      int64_t signal_window_ns,
      int64_t sampling_interval_ns)
      : midps_(y_horiz_ns),
        signals_(),
        signal_errors_(signal_window_ns),
        signal_errors_taker_buy_(signal_window_ns),
        signal_errors_taker_sell_(signal_window_ns) {
    y_horiz_ns_ = y_horiz_ns;
    signal_window_ns_ = signal_window_ns;

    signals_.ResizeWindow(y_horiz_ns_);
    signals_.SetPoppedCallback(
        std::bind(&SignalRMSEManager::OnSignalPopped,
        this,
        std::placeholders::_1,
        std::placeholders::_2));

    last_sampled_ts_ = 0L;
    sampling_interval_ns_ = sampling_interval_ns;

    last_logged_ts_ = 0L;
    log_interval_ns_ = 60 * Time::kNanosecondsPerSecond;
    rebooted_ts_ = 0L;
  }

  void SetPreRebootValues(
      int64_t timestamp,
      std::string product_name,
      coin::proto::StrategyStoredValue* ssv_loaded) {
    // rebooted_ts_ = timestamp;
    product_name_ = product_name;

    for (int i = 0; i < stored_value_type::N_TYPES; i++) {
      pre_reboot_stat_[i].last_logged_ts_ = last_logged_ts_;
      pre_reboot_stat_[i].log_interval_ns_ = log_interval_ns_;
      pre_reboot_stat_[i].strat_logger = strat_logger_;
      pre_reboot_stat_[i].product_name = product_name;
      pre_reboot_stat_[i].ma_type = i;
      switch (i) {
        case stored_value_type::SIGNAL_ERROR_MAKER:
          pre_reboot_stat_[i].ma_ptr = &signal_errors_;
          break;
        case stored_value_type::SIGNAL_ERROR_TAKER_BUY:
          pre_reboot_stat_[i].ma_ptr = &signal_errors_taker_buy_;
          break;
        case stored_value_type::SIGNAL_ERROR_TAKER_SELL:
          pre_reboot_stat_[i].ma_ptr = &signal_errors_taker_sell_;
          break;
        default:
          break;
      }
      pre_reboot_stat_[i].Deserialize(ssv_loaded[i]);
    }

    LOG(INFO) << timestamp << " SetPreRebootValues done for " << product_name_;
  }

  void SetStratLogger(
      exchange::base::strategy_util::StrategyLogger* strat_logger) {
    strat_logger_ = strat_logger;
  }

  void SetSimulation(bool simulation, bool disable_for_sim) {
    if (simulation && disable_for_sim) {
      for (int i = 0; i < stored_value_type::N_TYPES; i++) {
        pre_reboot_stat_[i].disable_logging = true;
      }
    }
  }

  void UpdateBook(int64_t timestamp, const IBook* book) {
    if (last_sampled_ts_ > 0 && last_sampled_ts_ + sampling_interval_ns_ > timestamp) {
      return;
    }
    if (!book->Ask0() || !book->Bid0()) return;

    last_sampled_ts_ = timestamp;
    double midp = 0.5*(book->Bid0()->price + book->Ask0()->price);

    midps_.Update(timestamp, midp);
  }

  void UpdateSignal(int64_t timestamp, double signal) {
    signals_.Update(timestamp, signal);
  }

  void UpdateTimeOnly(int64_t timestamp) {
    if (rebooted_ts_ == 0L) {
      rebooted_ts_ = timestamp;
    }
    midps_.UpdateTimeOnly(timestamp);
    signals_.UpdateTimeOnly(timestamp);
    signal_errors_.UpdateTimeOnly(timestamp);
    signal_errors_taker_buy_.UpdateTimeOnly(timestamp);
    signal_errors_taker_sell_.UpdateTimeOnly(timestamp);
  }

  bool GetSignalRMSE(
      int64_t timestamp,
      LmAggLogicConfig* lm_agg_config,
      double* rmses) {
    bool ma_ready = true;
    for (int i = 0; i < stored_value_type::N_TYPES; i++) {
      pre_reboot_stat_[i].Serialize(timestamp);
      if (!pre_reboot_stat_[i].ma_ready) {
        ma_ready = false;
        break;
      }
    }

    if (!ma_ready && !lm_agg_config->signal_rmse_use_pre_reboot_values()) {
      return false;
    }

    double ma_means[stored_value_type::N_TYPES];
    double ma_stds[stored_value_type::N_TYPES];

    int64_t warmup_sec = lm_agg_config->signal_rmse_warmup_sec();

    if (!lm_agg_config->signal_rmse_use_pre_reboot_values() &&
        rebooted_ts_ + warmup_sec * Time::kNanosecondsPerSecond > timestamp) {
      return false;
    }

    for (int i = 0; i < stored_value_type::N_TYPES; i++) {
      if (!pre_reboot_stat_[i].GetMeanStd(
              &ma_means[i], &ma_stds[i], lm_agg_config->signal_rmse_use_pre_reboot_values())) {
        return false;
      }
    }

    for (int i = 0; i < stored_value_type::N_TYPES; i++) {
      double sigerr_mean = ma_means[i];
      double sigerr_std = ma_stds[i];
      rmses[i] = std::sqrt(std::pow(sigerr_std, 2) + std::pow(sigerr_mean, 2));
    }

    return true;
  }

  enum stored_value_type {
    SIGNAL_ERROR_MAKER = 0,
    SIGNAL_ERROR_TAKER_BUY = 1,
    SIGNAL_ERROR_TAKER_SELL = 2,
    N_TYPES = 3
  };

 private:
  int64_t y_horiz_ns_;
  int64_t signal_window_ns_;
  presto::math::MovingAverageWindow<double> midps_;
  presto::math::CallbackMovingWindow<double> signals_;
  presto::math::MovingAverageWindow<double> signal_errors_;
  presto::math::MovingAverageWindow<double> signal_errors_taker_buy_;
  presto::math::MovingAverageWindow<double> signal_errors_taker_sell_;

  int64_t last_sampled_ts_;
  int64_t sampling_interval_ns_;

  exchange::base::strategy_util::StrategyLogger* strat_logger_;
  std::string product_name_;
  int64_t rebooted_ts_;
  int64_t last_logged_ts_;
  int64_t log_interval_ns_;
  PreRebootStat pre_reboot_stat_[stored_value_type::N_TYPES];

  void OnSignalPopped(int64_t timestamp, const double& signal) {
    double y_act = (midps_.backelem() - midps_.frontelem()) / midps_.frontelem() * 1e4;
    double err = y_act - signal;
    signal_errors_.Update(timestamp, err);

    if (signal > 0) {
      signal_errors_taker_buy_.Update(timestamp, std::min(0.0, err));
    } else if (signal < 0) {
      signal_errors_taker_sell_.Update(timestamp, std::max(0.0, err));
    }
  }
};

class FilldepthWindow {
 public:
  FilldepthWindow(
      int64_t window_sec,
      int64_t pred_ms,
      int64_t y_horiz_ns,
      int64_t signal_error_window_ns,
      int64_t reversion_lw_ns,
      int64_t reversion_sw_ns,
      int64_t tangent_ns,
      int64_t sampling_interval_ns)
      : book_bid0s_(pred_ms * Time::kNanosecondsPerMillisecond),
        book_ask0s_(pred_ms * Time::kNanosecondsPerMillisecond),
        buy_filled_prices_(pred_ms * Time::kNanosecondsPerMillisecond),
        sell_filled_prices_(pred_ms * Time::kNanosecondsPerMillisecond),
        buy_filldepths_(window_sec * Time::kNanosecondsPerSecond),
        sell_filldepths_(window_sec * Time::kNanosecondsPerSecond),
        midps_(y_horiz_ns),
        long_askps_(600 * Time::kNanosecondsPerSecond),
        long_bidps_(600 * Time::kNanosecondsPerSecond),
        signals_(),
        signal_errors_(signal_error_window_ns) {
    window_size_ns_ = window_sec * Time::kNanosecondsPerSecond;
    sampling_interval_ns_ = sampling_interval_ns;
    last_sampled_ts_ = 0L;

    last_logged_ts_ = 0L;
    log_interval_ns_ = 60 * Time::kNanosecondsPerSecond;
    rebooted_ts_ = 0L;

    y_horiz_ns_ = y_horiz_ns;
    signal_error_window_ns_ = signal_error_window_ns;

    signals_.ResizeWindow(y_horiz_ns_);
    signals_.SetPoppedCallback(
        std::bind(&FilldepthWindow::OnSignalPopped,
        this,
        std::placeholders::_1,
        std::placeholders::_2));

    reversion_lw_.reset(new presto::math::MovingAverageWindow<double>(reversion_lw_ns));
    reversion_sw_.reset(new presto::math::MovingAverageWindow<double>(reversion_sw_ns));
    tangent_lw_.reset(new presto::math::MovingAverageWindow<double>(tangent_ns));
    tangent_sw_.reset(new presto::math::MovingAverageWindow<double>(tangent_ns));

    F2_coeff_[0] = 0.5;
    for (int i = 1; i <= kPolyDegree_; i++) {
      F2_coeff_[i] = - F1_coeff_[i];
    }

    updated_ = false;
    randSeed_ = 0;
  }

  void SetPreRebootValues(
      int64_t timestamp,
      std::string product_name,
      coin::proto::StrategyStoredValue* ssv_loaded) {
    // rebooted_ts_ = timestamp;
    product_name_ = product_name;

    for (int i = 0; i < stored_value_type::N_TYPES; i++) {
      pre_reboot_stat_[i].last_logged_ts_ = last_logged_ts_;
      pre_reboot_stat_[i].log_interval_ns_ = log_interval_ns_;
      pre_reboot_stat_[i].strat_logger = strat_logger_;
      pre_reboot_stat_[i].product_name = product_name;
      pre_reboot_stat_[i].ma_type = i;
      switch (i) {
        case stored_value_type::BUY_FILLDEPTH:
          pre_reboot_stat_[i].ma_ptr = &buy_filldepths_;
          break;
        case stored_value_type::SELL_FILLDEPTH:
          pre_reboot_stat_[i].ma_ptr = &sell_filldepths_;
          break;
        case stored_value_type::SIGNAL_ERROR:
          pre_reboot_stat_[i].ma_ptr = &signal_errors_;
          break;
        default:
          break;
      }
      pre_reboot_stat_[i].Deserialize(ssv_loaded[i]);
    }

    LOG(INFO) << timestamp << " SetPreRebootValues done for " << product_name_;
  }

  void SetStratLogger(
      exchange::base::strategy_util::StrategyLogger* strat_logger) {
    strat_logger_ = strat_logger;
  }

  void UpdateSignal(int64_t timestamp, double signal) {
    signals_.PushElement(timestamp, signal);
  }

  void CheckAndUpdateWindows(int64_t timestamp) {
    if (book_bid0s_.num_popped() > 0 || book_bid0s_.num_popped() > 0 ||
        buy_filled_prices_.num_popped() > 0 || sell_filled_prices_.num_popped() > 0) {
      double first_bid0 = book_bid0s_.frontelem();
      double first_ask0 = book_ask0s_.frontelem();
      double min_bid_fillp = 0;
      double max_ask_fillp = 0;
      if (buy_filled_prices_.GetMinElement(&min_bid_fillp)) {
        buy_filldepths_.Update(timestamp, 1e4 * (min_bid_fillp - first_bid0) / first_bid0);
      }
      if (sell_filled_prices_.GetMaxElement(&max_ask_fillp)) {
        sell_filldepths_.Update(timestamp, 1e4 * (max_ask_fillp - first_ask0) / first_ask0);
      }
      updated_ = true;
    }
  }

  void UpdateBookp(int64_t timestamp, const IBook* book) {
    last_feed_ts_ = timestamp;
    if (last_sampled_ts_ > 0 && last_sampled_ts_ + sampling_interval_ns_ > timestamp) {
      return;
    }
    if (!book->Ask0() || !book->Bid0()) return;

    last_sampled_ts_ = timestamp;
    double midp = 0.5*(book->Bid0()->price + book->Ask0()->price);

    book_bid0s_.PushElement(timestamp, book->Bid0()->price);
    book_ask0s_.PushElement(timestamp, book->Ask0()->price);
    midps_.PushElement(timestamp, midp);
    long_askps_.PushElement(timestamp, book->Ask0()->price);
    long_bidps_.PushElement(timestamp, book->Bid0()->price);

    CheckAndUpdateWindows(timestamp);

    reversion_lw_->Update(timestamp, midp);
    reversion_sw_->Update(timestamp, midp);

    if (reversion_lw_->num_popped() > 0 && reversion_lw_->num_elements() > 0) {
      tangent_lw_->Update(timestamp, reversion_lw_->average());
    }
    if (reversion_sw_->num_popped() > 0 && reversion_sw_->num_elements() > 0) {
      tangent_sw_->Update(timestamp, reversion_sw_->average());
    }
  }

  void UpdateTradep(int64_t timestamp, const Trade& trade) {
    last_feed_ts_ = timestamp;
    double signed_qty = trade.signed_qty();
    if (signed_qty > 0) {  // buy trade: ask order filled
      sell_filled_prices_.Update(timestamp, trade.price);
    } else if (signed_qty < 0) {  // sell trade: bid order filled
      buy_filled_prices_.Update(timestamp, trade.price);
    }
    // CheckAndUpdateWindows(timestamp);
  }

  void UpdateTimeOnly(int64_t timestamp) {
    if (rebooted_ts_ == 0L) {
      rebooted_ts_ = timestamp;
    }
    buy_filled_prices_.UpdateTimeOnly(timestamp);
    sell_filled_prices_.UpdateTimeOnly(timestamp);
    book_bid0s_.UpdateTimeOnly(timestamp);
    book_ask0s_.UpdateTimeOnly(timestamp);

    buy_filldepths_.UpdateTimeOnly(timestamp);
    sell_filldepths_.UpdateTimeOnly(timestamp);

    midps_.UpdateTimeOnly(timestamp);
    long_askps_.UpdateTimeOnly(timestamp);
    long_bidps_.UpdateTimeOnly(timestamp);
    signals_.UpdateTimeOnly(timestamp);
    signal_errors_.UpdateTimeOnly(timestamp);
  }

  bool GetEdge(
      int64_t timestamp,
      double midret_pred,
      double* signal_lb_p,
      double* signal_ub_p,
      double ask_skew_edge_bps,
      double bid_skew_edge_bps,
      LmAggLogicConfig* lm_agg_config,
      std::optional<double>* buy_filldepth_pred,
      std::optional<double>* sell_filldepth_pred,
      double* buy_edge,
      double* sell_edge,
      double* buy_qty_multiplier,
      double* sell_qty_multiplier) {
    bool ma_ready = true;
    for (int i = 0; i < stored_value_type::N_TYPES; i++) {
      pre_reboot_stat_[i].Serialize(timestamp);
      if (!pre_reboot_stat_[i].ma_ready) {
        ma_ready = false;
        break;
      }
    }

    if (!ma_ready && !lm_agg_config->use_pre_reboot_values()) {
      return false;
    }

    double signal_lb = *signal_lb_p;
    double signal_ub = *signal_ub_p;

    // double midp = midps_.backelem();
    double midp = 0.5 * (long_askps_.backelem() + long_bidps_.backelem());

    double ma_means[stored_value_type::N_TYPES];
    double ma_stds[stored_value_type::N_TYPES];

    int64_t warmup_sec = lm_agg_config->filldepth_window_warmup_sec();

    if (!lm_agg_config->use_pre_reboot_values() &&
        rebooted_ts_ + warmup_sec * Time::kNanosecondsPerSecond > timestamp) {
      return false;
    }

    for (int i = 0; i < stored_value_type::N_TYPES; i++) {
      if (!pre_reboot_stat_[i].GetMeanStd(
              &ma_means[i], &ma_stds[i], lm_agg_config->use_pre_reboot_values())) {
        return false;
      }
    }

    if (!updated_) {
      return true;
    }

    double fee = lm_agg_config->filldepth_window_fee();
    double multiplier = lm_agg_config->filldepth_window_multiplier();
    double signal_multiplier = lm_agg_config->signal_multiplier();
    double hspd_weight = lm_agg_config->filldepth_window_hspd_weight();
    double err_multiplier = lm_agg_config->filldepth_window_err_multiplier();
    double block_thres = lm_agg_config->filldepth_window_block_threshold();
    int use_simple = lm_agg_config->filldepth_window_use_simple_ver();
    double extra_edge = lm_agg_config->filldepth_window_manual_extra_edge();
    double reversion_weight = lm_agg_config->reversion_weight();
    double tangent_multiplier = lm_agg_config->tangent_multiplier();
    double reversion_signal_multiplier = lm_agg_config->reversion_signal_multiplier();


    // bool reversion_block = false;
    double reversion_signal = 0;
    if (reversion_weight > 0) {
      if (reversion_lw_->num_popped() == 0) {
        return false;
      }
      double ma_lw = reversion_lw_->average();
      double ma_sw = reversion_sw_->average();

      if (tangent_lw_->num_popped() == 0) {
        return false;
      }
      double m_front = tangent_lw_->frontelem();
      double m_back = tangent_lw_->backelem();
      double tangent_lw = (m_back - m_front) / m_front * 1e4;
      m_front = tangent_sw_->frontelem();
      m_back = tangent_sw_->backelem();
      double tangent_sw = (m_back - m_front) / m_front * 1e4;

      double tangent_adj = tangent_lw * tangent_multiplier;
      reversion_signal = (ma_lw * (1 + tangent_adj * 1e-4) - midp) / midp * 1e4;
      reversion_signal *= reversion_signal_multiplier;

      if (lm_agg_config->use_reversion_block()) {
        if (tangent_lw > 0 && tangent_sw > tangent_lw && midp > ma_lw && ma_sw > ma_lw) {
          reversion_signal = 0;
        } else if (tangent_lw < 0 && tangent_sw < tangent_lw && midp < ma_lw && ma_sw < ma_lw) {
          reversion_signal = 0;
        }
      }

      signal_lb *= (1 - reversion_weight);
      signal_lb += reversion_signal * reversion_weight;
      signal_lb += bid_skew_edge_bps;

      *signal_lb_p = signal_lb;

      signal_ub *= (1 - reversion_weight);
      signal_ub += reversion_signal * reversion_weight;
      signal_ub += ask_skew_edge_bps;

      *signal_ub_p = signal_ub;
    }

    double askp, bidp;
    if (long_askps_.empty() || long_bidps_.empty()) {
      return false;
    } else {
      askp = long_askps_.backelem();
      bidp = long_bidps_.backelem();
    }
    double hspd = 0.5 * (askp - bidp) / midp * 1e4 * hspd_weight;

    double midret_pred_for_buy = signal_lb;
    double midret_pred_for_sell = signal_ub;
    double signal_lb_len = midret_pred - signal_lb;
    double signal_ub_len = signal_ub - midret_pred;

    if (lm_agg_config->filldepth_window_selective_multiplier()) {
      if (midret_pred > 0) {
        midret_pred_for_sell = midret_pred * signal_multiplier + signal_ub_len;
      } else {
        midret_pred_for_buy = midret_pred * signal_multiplier - signal_lb_len;
      }
    } else {
      midret_pred_for_sell = midret_pred * signal_multiplier + signal_ub_len;
      midret_pred_for_buy = midret_pred * signal_multiplier - signal_lb_len;
    }

    if (lm_agg_config->filldepth_window_use_qty_multiplier()) {
      if (midret_pred_for_buy < 0) {
        *buy_qty_multiplier = 0.5;
      }
      if (midret_pred_for_sell > 0) {
        *sell_qty_multiplier = 0.5;
      }
    }

    double sigerr_mean = ma_means[stored_value_type::SIGNAL_ERROR];
    double sigerr_std = ma_stds[stored_value_type::SIGNAL_ERROR];

    if (lm_agg_config->filldepth_window_use_rmse()) {
      sigerr_std = std::sqrt(std::pow(sigerr_std, 2) + std::pow(sigerr_mean, 2));
    }

    if (use_simple) {
      if (*buy_filldepth_pred) {
        *buy_edge = **buy_filldepth_pred - err_multiplier * sigerr_std - extra_edge;
      }
      if (*sell_filldepth_pred) {
        *sell_edge = **sell_filldepth_pred + err_multiplier * sigerr_std + extra_edge;
      }

      if (block_thres > 0) {
        if (midret_pred_for_buy < -block_thres) {
          *buy_edge = -1000;
        }
        if (midret_pred_for_sell > block_thres) {
          *sell_edge = 1000;
        }
      }

      return true;
    }

    double buy_filldepth_mean = ma_means[stored_value_type::BUY_FILLDEPTH];
    double buy_filldepth_std = ma_stds[stored_value_type::BUY_FILLDEPTH];
    double sell_filldepth_mean = ma_means[stored_value_type::SELL_FILLDEPTH];
    double sell_filldepth_std = ma_stds[stored_value_type::SELL_FILLDEPTH];

    buy_filldepth_mean -= lm_agg_config->filldepth_window_mean_shift_bps();
    sell_filldepth_mean += lm_agg_config->filldepth_window_mean_shift_bps();

    if (*buy_filldepth_pred) {
      buy_filldepth_mean = 0.5 * (buy_filldepth_mean + **buy_filldepth_pred);
    }
    if (*sell_filldepth_pred) {
      sell_filldepth_mean = 0.5 * (sell_filldepth_mean + **sell_filldepth_pred);
    }

    buy_filldepth_std *= multiplier;
    buy_filldepth_std += sigerr_std * err_multiplier;

    sell_filldepth_std *= multiplier;
    sell_filldepth_std += sigerr_std * err_multiplier;

    double a_buy = -bidp / midp * buy_filldepth_std;
    double b_buy = hspd + midret_pred_for_buy - bidp/midp * buy_filldepth_mean - fee;
    double a_sell = askp / midp * sell_filldepth_std;
    double b_sell = hspd - midret_pred_for_sell + askp/midp * sell_filldepth_mean - fee;

    // a_buy *= multiplier;
    // b_buy *= multiplier;
    // a_sell *= multiplier;
    // b_sell *= multiplier;

    bool buy_edge_calculatable = true;
    if (a_buy * (-z_range_) + b_buy < 0) {
      buy_edge_calculatable = false;
    }
    // if (*sh_signal) {
    //   if (**sh_signal < -sh_thres) {
    //     buy_edge_calculatable = false;
    //   }
    // }

    bool sell_edge_calculatable = true;
    if (a_sell * z_range_ + b_sell < 0) {
      sell_edge_calculatable = false;
    }
    // if (*sh_signal) {
    //   if (**sh_signal > sh_thres) {
    //     sell_edge_calculatable = false;
    //   }
    // }

    if (!buy_edge_calculatable && !sell_edge_calculatable) {
      return true;
    }

    for (int i = 0; i <= kPolyDegree_; i++) {
      obj_fn_buy_[i] = F1_coeff_[i] * b_buy;
      obj_fn_sell_[i] = F2_coeff_[i] * b_sell;
    }
    obj_fn_buy_[kPolyDegree_ + 1] = 0;
    obj_fn_sell_[kPolyDegree_ + 1] = 0;
    for (int i = 0; i <= kPolyDegree_; i++) {
      obj_fn_buy_[i+1] += F1_coeff_[i] * a_buy;
      obj_fn_sell_[i+1] += F2_coeff_[i] * a_sell;
    }

    double obj_fn_buy_d[kPolyDegree_ + 1];
    double obj_fn_sell_d[kPolyDegree_ + 1];

    for (int i = 1; i <= kPolyDegree_ + 1; i++) {
      obj_fn_buy_d[i-1] = obj_fn_buy_[i] * i;
      obj_fn_sell_d[i-1] = obj_fn_sell_[i] * i;
    }

    double obj_fn_buy_dd[kPolyDegree_];
    double obj_fn_sell_dd[kPolyDegree_];

    for (int i = 1; i <= kPolyDegree_; i++) {
      obj_fn_buy_dd[i-1] = obj_fn_buy_d[i] * i;
      obj_fn_sell_dd[i-1] = obj_fn_sell_d[i] * i;
    }


    double g_buy[2] = {b_buy, a_buy};
    double g_sell[2] = {b_sell, a_sell};

    // std::string buy_str2 = fn_to_string(g_buy, 2);
    // std::string sell_str2 = fn_to_string(g_sell, 2);
    // LOG(INFO) << "buy_profit_fn : " << buy_str2;
    // LOG(INFO) << "sell_profit_fn : " << sell_str2;

    double buy_root = -100;
    double sell_root = 100;

    // for buy
    if (buy_edge_calculatable) {
      double maxval = 0.0;
      double cands[2] = {
          ((askp - bidp) / bidp * 1e4 - buy_filldepth_mean) / buy_filldepth_std,
          -2.0};
      for (double root : cands) {
        double altval = std::erfc(-root/std::sqrt(2))/2 * poly_eval(g_buy, 2, root);

        if (altval > maxval) {
          maxval = altval;
          *buy_edge = root * buy_filldepth_std + buy_filldepth_mean;
          if (root == 2.0 || root == -2.0) {
            *buy_edge -= sigerr_std;
          }
          buy_root = root;
        }
      }
      if (maxval == 0) {
        *buy_edge = -1000;
      }

      int iter = 0, max_iter = 10;
      double x0, x;

      double left = -z_range_;
      double right = z_range_;
      double delta = z_range_ / 20;

      while (poly_eval(obj_fn_buy_d, kPolyDegree_+1, right) > 0 && right > left) {
        right -= delta;
      }
      while (poly_eval(obj_fn_buy_d, kPolyDegree_+1, left) < 0 && right > left) {
        left += delta;
      }

      if (right > left) {
        x = 0.5 * (left + right);

        double slope, value;
        do {
          iter++;
          x0 = x;

          value = poly_eval(obj_fn_buy_d, kPolyDegree_+1, x0);
          if (value < 0.001 && value > -0.001) {
            break;
          }

          slope = poly_eval(obj_fn_buy_dd, kPolyDegree_, x0);
          if (slope < 0.001 && slope > -0.001) {
            x = static_cast<double> (rand_r(&randSeed_) / RAND_MAX * 4 - 2);
          } else {
            x = x0 - value/slope;
          }
        } while (iter < max_iter);

        double alt = poly_eval(obj_fn_buy_, kPolyDegree_+2, x);
        double altp = bidp * (1 + 1e-4 * (x * buy_filldepth_std + buy_filldepth_mean));
        if (alt > maxval && x < z_range_ && x > -z_range_ && (altp < askp)) {
          buy_root = x;
          *buy_edge = buy_root * buy_filldepth_std + buy_filldepth_mean;
        }
      }
    }

    // for sell
    if (sell_edge_calculatable) {
      double maxval = 0.0;
      double cands[2] = {
          ((bidp - askp) / askp * 1e4 - sell_filldepth_mean) / sell_filldepth_std,
          2.0};
      for (double root : cands) {
        double altval = (1 - std::erfc(-root/std::sqrt(2))/2) * poly_eval(g_sell, 2, root);

        if (altval > maxval) {
          maxval = altval;
          *sell_edge = root * sell_filldepth_std + sell_filldepth_mean;
          if (root == 2.0 || root == -2.0) {
            *sell_edge += sigerr_std;
          }
          sell_root = root;
        }
      }
      if (maxval == 0) {
        *sell_edge = 1000;
      }

      int iter = 0, max_iter = 10;
      double x0, x = 0.0;

      double left = -z_range_;
      double right = z_range_;
      double delta = z_range_ / 20;

      while (poly_eval(obj_fn_sell_d, kPolyDegree_+1, right) > 0 && right > left) {
        right -= delta;
      }
      while (poly_eval(obj_fn_sell_d, kPolyDegree_+1, left) < 0 && right > left) {
        left += delta;
      }

      if (right > left) {
        x = 0.5 * (left + right);

        double slope, value;
        do {
          iter++;
          x0 = x;

          value = poly_eval(obj_fn_sell_d, kPolyDegree_+1, x0);
          if (value < 0.001 && value > -0.001) {
            break;
          }

          slope = poly_eval(obj_fn_sell_dd, kPolyDegree_, x0);
          if (slope < 0.001 && slope > -0.001) {
            x = static_cast<double> (rand_r(&randSeed_) / RAND_MAX * 4 - 2);
          } else {
            x = x0 - value/slope;
          }
        } while (iter < max_iter);

        double alt = poly_eval(obj_fn_sell_, kPolyDegree_+2, x);
        double altp = askp * (1 + 1e-4 * (x * sell_filldepth_std + sell_filldepth_mean));
        if (alt > maxval && x < z_range_ && x > -z_range_ && (altp > bidp)) {
          sell_root = x;
          *sell_edge = sell_root * sell_filldepth_std + sell_filldepth_mean;
        }
      }
    }

    *buy_edge -= extra_edge;
    *sell_edge += extra_edge;

    if (block_thres > 0) {
      if (midret_pred_for_buy < -block_thres) {
        *buy_edge = -1000;
      }
      if (midret_pred_for_sell > block_thres) {
        *sell_edge = 1000;
      }
    }

    updated_ = false;
    return true;
  }

  enum stored_value_type {
    BUY_FILLDEPTH = 0,
    SELL_FILLDEPTH = 1,
    SIGNAL_ERROR = 2,
    N_TYPES = 3
  };

 private:
  std::string product_name_;
  int64_t window_size_ns_;
  int64_t sampling_interval_ns_;
  int64_t y_horiz_ns_;
  int64_t signal_error_window_ns_;
  int64_t last_sampled_ts_;
  int64_t last_logged_ts_;
  int64_t log_interval_ns_;
  int64_t last_feed_ts_ = 0L;
  presto::math::MovingAverageWindow<double> book_bid0s_;
  presto::math::MovingAverageWindow<double> book_ask0s_;
  presto::math::TimeWindowMovingMaximum<double> buy_filled_prices_;
  presto::math::TimeWindowMovingMaximum<double> sell_filled_prices_;
  presto::math::MovingAverageWindow<double> buy_filldepths_;
  presto::math::MovingAverageWindow<double> sell_filldepths_;
  presto::math::MovingAverageWindow<double> midps_, long_askps_, long_bidps_;
  presto::math::CallbackMovingWindow<double> signals_;
  presto::math::MovingAverageWindow<double> signal_errors_;
  std::unique_ptr<presto::math::MovingAverageWindow<double>> reversion_lw_;
  std::unique_ptr<presto::math::MovingAverageWindow<double>> reversion_sw_;
  std::unique_ptr<presto::math::MovingAverageWindow<double>> tangent_lw_;
  std::unique_ptr<presto::math::MovingAverageWindow<double>> tangent_sw_;

  int64_t rebooted_ts_;
  exchange::base::strategy_util::StrategyLogger* strat_logger_;
  PreRebootStat pre_reboot_stat_[3];

  unsigned int randSeed_;
  static const int kPolyDegree_ = 13;  // polynomial (for normal dist cdf) highest degree
  static constexpr double z_range_ = 2;
  static constexpr double F1_coeff_[kPolyDegree_ + 1] =
      {0.5, 3.98942e-1, 0, -6.64904e-2, 0, 9.97356e-3, 0,
      -1.18733e-3, 0, 1.15435e-4, 0, -9.44466e-6, 0, 6.65969e-7};
  double F2_coeff_[kPolyDegree_ + 1];
  double obj_fn_buy_[kPolyDegree_ + 2];
  double obj_fn_sell_[kPolyDegree_ + 2];
  bool updated_;
  std::string fn_to_string(const double* coeff, int len) {
    std::string str = "y=";
    for (int i = 0; i < len; i++) {
      double d = coeff[i];
      if (i == 0) {
        str += std::to_string(d);
      } else {
        str += " +(" + std::to_string(d) + ")*x^{" + std::to_string(i) + "}";
      }
    }
    return str;
  }
  double poly_eval(const double c[], const int len, const double x) {
    int i;
    double ans = c[len-1];
    for (i = len - 1; i > 0; i--) ans = c[i-1] + x * ans;
    return ans;
  }
  void OnSignalPopped(int64_t timestamp, const double& signal) {
    if (midps_.num_popped() == 0) return;
    if (last_feed_ts_ + 60'000'000'000LL < timestamp) return;
    double y_act = 0.0;
    if (midps_.num_elements() > 0) {
      y_act = (midps_.backelem() - midps_.frontelem()) / midps_.frontelem() * 1e4;
    }
    double err = y_act - signal;
    signal_errors_.Update(timestamp, err);
  }
};

inline bool is_l1_book(const IBook& book) {
  return (book.AskSize() <= 1 && book.BidSize() <= 1);
}

inline double get_available_qty(
    const IBook& book,
    double price,
    coin::proto::OrderDirection direction) {
  double sum = 0;
  if (direction == coin::proto::OrderDirection::DIRECTION_BUY) {
    for (const auto& [p, q] : book.Asks()) {
      if (p > price + 1e-8) {
        break;
      }
      sum += q;
    }
  } else {
    for (const auto& [p, q] : book.Bids()) {
      if (p + 1e-8 < price) {
        break;
      }
      sum += q;
    }
  }
  return sum;
}

class PositionMismatchCalculator {
 public:
  PositionMismatchCalculator(
      double min_pos, double max_pos, double lot_size)
    : ready_(false),
      min_pos_(min_pos),
      max_pos_(max_pos),
      lot_size_(lot_size) {
    const int64_t tt = 300;
    desired_buy_qtys_.ResizeWindow(tt * 1e9);
    desired_sell_qtys_.ResizeWindow(tt * 1e9);
    actual_buy_qtys_.ResizeWindow(tt * 1e9);
    actual_sell_qtys_.ResizeWindow(tt * 1e9);
  }

  void UpdateTimeOnly(int64_t timestamp) {
    desired_buy_qtys_.UpdateTimeOnly(timestamp);
    desired_sell_qtys_.UpdateTimeOnly(timestamp);
    actual_buy_qtys_.UpdateTimeOnly(timestamp);
    actual_sell_qtys_.UpdateTimeOnly(timestamp);
  }

  void UpdateFill(int64_t timestamp, int sign, double fill_qty) {
    ready_ = true;
    if (sign == 1) {
      actual_buy_qtys_.Update(timestamp, fill_qty);
    } else if (sign == -1) {
      actual_sell_qtys_.Update(timestamp, fill_qty);
    }
    UpdateTimeOnly(timestamp);
  }

  void UpdateSubmitOrder(int64_t timestamp, int sign, double order_qty) {
    if (sign == 1) {
      desired_buy_qtys_.Update(timestamp, order_qty);
    } else if (sign == -1) {
      desired_sell_qtys_.Update(timestamp, order_qty);
    }
    UpdateTimeOnly(timestamp);
  }

  double GetMismatchInStackCount() const {
    if (min_pos_ == max_pos_ || !ready_) {
      return 0;
    }
    double dbq = desired_buy_qtys_.sum();
    double dsq = desired_sell_qtys_.sum();
    double abq = actual_buy_qtys_.sum();
    double asq = actual_sell_qtys_.sum();

    if (dbq == 0 || dsq == 0) {
      return 0;
    }

    double fill_ratio = (abq + asq) / (dbq + dsq);
    double imbalance_from_const_fr = (abq - asq) - fill_ratio * (dbq - dsq);
    double imbalance_in_stack = imbalance_from_const_fr / lot_size_;
    /*
    LOG(INFO) <<
        fmt::format(
            "fill_ratio {} actual {} desired {} imbalance_stack {} "
            , fill_ratio, (abq - asq), fill_ratio * (dbq - dsq), imbalance_in_stack);*/
    return imbalance_in_stack;
  }

 private:
  bool ready_;
  const double min_pos_;
  const double max_pos_;
  const double lot_size_;
  presto::math::TimeWindowMovingAverage<double> desired_buy_qtys_;
  presto::math::TimeWindowMovingAverage<double> desired_sell_qtys_;
  presto::math::TimeWindowMovingAverage<double> actual_buy_qtys_;
  presto::math::TimeWindowMovingAverage<double> actual_sell_qtys_;
};


class StackQueue {
 public:
  explicit StackQueue(const LmFocusMidfreqConfig& focus_midfreq_config)
      : focus_midfreq_config(focus_midfreq_config),
        pos_sum(0) {}

  void Update(
      int64_t timestamp,
      coin::proto::TradeSide trade_side,
      double qty) {
    UpdatePosition(timestamp, trade_side, qty);

    if (queue_.empty()) {
      PushQty(timestamp, trade_side, qty);
      return;
    }
    double old_qty = queue_.begin()->second;

    if ((old_qty > 0 && trade_side == coin::proto::TRADE_BUY_SIDE) ||
        (old_qty < 0 && trade_side == coin::proto::TRADE_SELL_SIDE)) {
        PushQty(timestamp, trade_side, qty);
    } else {
      while (qty > 0 && !queue_.empty()) {
        old_qty = queue_.begin()->second;
        double sign = (old_qty > 0) ? -1 : 1;
        if (qty >= std::abs(old_qty)) {
          qty += sign * old_qty;
          queue_.pop_front();
        } else {
          queue_.begin()->second += sign * qty;
          qty = 0;
        }
      }
      if (qty > 0) {
        PushQty(timestamp, trade_side, qty);
      }
    }
  }

  void UpdateStack(int64_t timestamp, double lot_size) {
    if (queue_.empty()) {
      return;
    }
    double current_stack = pos_sum / lot_size;
    if (std::abs(current_stack) <= 0.5) {
      while (queue_.size() > 1) {
        queue_.pop_front();
      }
      auto& front = queue_.front();
      front.first = timestamp;
      front.second = pos_sum;
    }
  }

  double GetFirstTimestampDiff(int64_t timestamp) {
    if (queue_.empty()) {
      return 0.0;
    }
    int64_t diff = timestamp - queue_.begin()->first;
    return diff / 1'000'000'000LL;
  }

  double GetHoldingLean(int64_t timestamp) {
    double diff = GetFirstTimestampDiff(timestamp);
    diff = std::max(diff - focus_midfreq_config.holding_lean_window_in_sec(), 0.0);
    return focus_midfreq_config.holding_lean_bps() * diff;
  }

  void LoggingQueue() {
    std::string msg;
    for (const auto& item : queue_) {
      msg += std::to_string(item.second) + " ";
    }
    LOG(INFO) << fmt::format("queue: {}", msg);
  }

  double GetPosition() {
    return pos_sum;
  }

  void Logging() {
    if (queue_.empty()) {
      return;
    }
    double qty_sum = GetPosition();
    const auto& first = queue_.front();
    const auto& last = queue_.back();
    LOG(INFO) << fmt::format(
        "sum_current_stack: {}, first_ts: {}, first_lot: {}, "
        "last_ts: {}, last_lot: {}",
        qty_sum, first.first, first.second, last.first, last.second);
  }

 private:
  void PushQty(
      int64_t timestamp,
      coin::proto::TradeSide trade_side,
      double qty) {
    if (trade_side == coin::proto::TRADE_BUY_SIDE) {
      queue_.push_back(std::make_pair(timestamp, qty));
    } else if (trade_side == coin::proto::TRADE_SELL_SIDE) {
      queue_.push_back(std::make_pair(timestamp, -qty));
    }
  }

  void UpdatePosition(
      int64_t timestamp,
      coin::proto::TradeSide trade_side,
      double qty) {
    if (trade_side == coin::proto::TRADE_BUY_SIDE) {
      pos_sum += qty;
    } else if (trade_side == coin::proto::TRADE_SELL_SIDE) {
      pos_sum -= qty;
    }
  }

  LmFocusMidfreqConfig focus_midfreq_config;
  double pos_sum;
  std::deque<std::pair<int64_t, double>> queue_;
};


}  // namespace coin2::strategy::linear_model
